package ctrl

import (
	"encoding/json"
	"log"
	"net"
	"net/http"
	"strconv"
	"sync"

	"github.com/gorilla/websocket"
	"gopkg.in/fatih/set.v0"
)

// 消息类型
const (
	CMD_SINGLE_MSG = 10 //单聊
	CMD_ROOM_MSG   = 11 //群聊
	CMD_HEART      = 0  //心跳
)

type Message struct {
	Id      int64  `json:"id,omitempty" form:"id"`           //消息ID
	Userid  int64  `json:"userid,omitempty" form:"userid"`   //谁发的
	Cmd     int    `json:"cmd,omitempty" form:"cmd"`         //群聊还是私聊
	Dstid   int64  `json:"dstid,omitempty" form:"dstid"`     //对端用户ID/群ID
	Media   int    `json:"media,omitempty" form:"media"`     //消息按照什么样式展示
	Content string `json:"content,omitempty" form:"content"` //消息的内容
	Pic     string `json:"pic,omitempty" form:"pic"`         //预览图片
	Url     string `json:"url,omitempty" form:"url"`         //服务的URL
	Memo    string `json:"memo,omitempty" form:"memo"`       //简单描述
	Amount  int    `json:"amount,omitempty" form:"amount"`   //其他和数字相关的
}

/**
消息发送结构体
1、MEDIA_TYPE_TEXT
{id:1,userid:2,dstid:3,cmd:10,media:1,content:"hello"}
2、MEDIA_TYPE_News
{id:1,userid:2,dstid:3,cmd:10,media:2,content:"标题",pic:"http://www.baidu.com/a/log,jpg",url:"http://www.a,com/dsturl","memo":"这是描述"}
3、MEDIA_TYPE_VOICE，amount单位秒
{id:1,userid:2,dstid:3,cmd:10,media:3,url:"http://www.a,com/dsturl.mp3",anount:40}
4、MEDIA_TYPE_IMG
{id:1,userid:2,dstid:3,cmd:10,media:4,url:"http://www.baidu.com/a/log,jpg"}
5、MEDIA_TYPE_REDPACKAGR //红包amount 单位分
{id:1,userid:2,dstid:3,cmd:10,media:5,url:"http://www.baidu.com/a/b/c/redpackageaddress?id=100000","amount":300,"memo":"恭喜发财"}
6、MEDIA_TYPE_EMOJ 6
{id:1,userid:2,dstid:3,cmd:10,media:6,"content":"cry"}
7、MEDIA_TYPE_Link 6
{id:1,userid:2,dstid:3,cmd:10,media:7,"url":"http://www.a,com/dsturl.html"}

7、MEDIA_TYPE_Link 6
{id:1,userid:2,dstid:3,cmd:10,media:7,"url":"http://www.a,com/dsturl.html"}

8、MEDIA_TYPE_VIDEO 8
{id:1,userid:2,dstid:3,cmd:10,media:8,pic:"http://www.baidu.com/a/log,jpg",url:"http://www.a,com/a.mp4"}

9、MEDIA_TYPE_CONTACT 9
{id:1,userid:2,dstid:3,cmd:10,media:9,"content":"10086","pic":"http://www.baidu.com/a/avatar,jpg","memo":"胡大力"}

*/

// 本核心在于形成userid和Node的映射关系
type Node struct {
	Conn *websocket.Conn
	//并行转串行,
	DataQueue chan []byte
	GroupSets set.Interface
}

// 映射关系表
var clientMap map[int64]*Node = make(map[int64]*Node, 0)

// 读写锁
var rwlocker sync.RWMutex

// 页面接入ws://127.0.0.1/chat?id=1&token=xxxx
func Chat(writer http.ResponseWriter,
	request *http.Request) {
	//fmt.Printf("%+v", request.Header)
	log.Println("Chat start.")

	//检验接入是否合法
	query := request.URL.Query()
	id := query.Get("id")
	token := query.Get("token")
	userId, _ := strconv.ParseInt(id, 10, 64)
	isvalida := checkToken(userId, token)
	log.Printf("query userId: %d, token: %s, isvalida: %t\n", userId, token, isvalida)

	//将http协议升级为websocket协议
	conn, err := (&websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			return isvalida
		},
	}).Upgrade(writer, request, nil)
	if err != nil {
		log.Println(err.Error())
		return
	}

	//记录用户连接信息
	node := &Node{
		Conn:      conn,
		DataQueue: make(chan []byte, 50),
		GroupSets: set.New(set.ThreadSafe),
	}

	//获取用户全部群Id
	comIds := contactService.SearchComunityIds(userId)
	for _, v := range comIds {
		node.GroupSets.Add(v)
	}

	//userid和node形成绑定关系
	rwlocker.Lock()
	clientMap[userId] = node
	rwlocker.Unlock()

	//开启协程发送消息
	go sendproc(node)

	//开启协程接收消息
	go recvproc(node)

	//打招呼
	sendMsg(userId, []byte("hello! this go-chat-im"))
}

// 添加新的群ID到用户的groupset中
func AddGroupId(userId, gid int64) {
	//取得node
	rwlocker.Lock()
	node, ok := clientMap[userId]
	if ok {
		node.GroupSets.Add(gid)
	}
	//clientMap[userId] = node
	rwlocker.Unlock()
	//添加gid到set
}

// ws发送协程
func sendproc(node *Node) {
	log.Println("sendproc listening.")
	// for {
	// 	select {
	// 	case data := <-node.DataQueue:
	// 		err := node.Conn.WriteMessage(websocket.TextMessage, data)
	// 		if err != nil {
	// 			log.Println(err.Error())
	// 			return
	// 		}
	// 	}
	// }

	//自动从通道接收值，直到通道关闭，此时循环结束
	for data := range node.DataQueue {
		err := node.Conn.WriteMessage(websocket.TextMessage, data)
		log.Println("sendproc WriteMessage.")
		if err != nil {
			log.Println(err.Error())
			return
		}
	}
}

// ws接收协程
func recvproc(node *Node) {
	log.Println("recvproc listening.")
	for {
		_, data, err := node.Conn.ReadMessage()
		if err != nil {
			log.Println(err.Error())
			return
		}
		//1. 单体应用
		//dispatch(data)

		//2. 协程模式：把消息广播到局域网
		log.Printf("recvproc ReadMessage: %s\n", data)
		broadMsg(data)
	}
}

// 启动两个upd协程
func init() {
	log.Println("chat init start.")
	go udpsendproc()
	go udprecvproc()
}

// 存放要广播的数据
var udpsendchan chan []byte = make(chan []byte, 1024)

// 将消息广播到局域网
func broadMsg(data []byte) {
	log.Println("broadMsg run.")
	udpsendchan <- data
}

// udp数据发送
func udpsendproc() {
	log.Println("udpsendproc listening.")

	//使用udp协议拨号
	con, err := net.DialUDP("udp", nil,
		&net.UDPAddr{
			IP:   net.IPv4zero, //udp这个对外开放的ip和端口号 net.IPv4(192, 168, 0, 255), //代表的是个网段,255代表的是个子网段
			Port: 3000,
		})
	//defer con.Close()
	if err != nil {
		log.Println(err.Error())
		return
	}

	defer con.Close()

	//通过得到的con发送消息
	// for {
	// 	select {
	// 	case data := <-udpsendchan:
	// 		_, err = con.Write(data)
	// 		log.Println("udpsendproc-Write")
	// 		if err != nil {
	// 			log.Println(err.Error())
	// 			return
	// 		}
	// 	}
	// }

	for data := range udpsendchan {
		_, err = con.Write(data)
		log.Println("udpsendproc write")
		if err != nil {
			log.Println(err.Error())
			return
		}
	}
}

// upd接收并处理功能
func udprecvproc() {
	log.Println("udprecvproc listening.")

	//监听udp广播端口
	con, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4zero,
		Port: 3000,
	})
	//defer con.Close()
	if err != nil {
		log.Println(err.Error())
	}

	defer con.Close()

	//处理端口发过来的数据
	for {
		var buf [512]byte
		n, err := con.Read(buf[0:])
		if err != nil {
			log.Println(err.Error())
			return
		}

		//直接数据处理
		log.Println("udprecvproc read")
		dispatch(buf[0:n])
	}
}

// 后端调度逻辑处理
func dispatch(data []byte) {
	log.Println("dispatch run.")

	//解析data为message
	msg := Message{}
	err := json.Unmarshal(data, &msg)
	if err != nil {
		log.Println(err.Error())
		return
	}

	//根据cmd对逻辑进行处理
	switch msg.Cmd {
	case CMD_SINGLE_MSG: //单聊
		sendMsg(msg.Dstid, data)
	case CMD_ROOM_MSG: //群聊
		rwlocker.Lock()
		for uid, v := range clientMap {
			//排除自己不发送
			if uid == msg.Userid {
				continue
			}
			if v.GroupSets.Has(msg.Dstid) {
				v.DataQueue <- data
			}
		}
		rwlocker.Unlock()
	case CMD_HEART: //心跳
		//一般啥都不做
	}
}

// 发送消息
func sendMsg(userId int64, msg []byte) {
	log.Printf("sendMsg run. msg: %v\n", string(msg))
	rwlocker.RLock()
	node, ok := clientMap[userId]
	rwlocker.RUnlock()
	if ok {
		node.DataQueue <- msg
	}
}

// 检测是否有效
func checkToken(userId int64, token string) bool {
	//从数据库里面查询并比对
	user := userService.Find(userId)
	return user.Token == token
}
